#pragma once
#include "GameComponent.h"
#include "Rigidbody.h"
#include "physics/Physics.h"
#include "SpriteAnimation.h"
#include "Event_PlayerDied.h"




class PlayerController : public Boon::GameComponent, public IObserverPhysics
{
	GAMECOMPONENT( PlayerController, Boon::GameComponent )

	enum Facing
	{
		FACE_LEFT,
		FACE_RIGHT
	};

	enum JumpCheckState
	{
		JUMP_CHECKED,
		JUMP_NOT_AVAILABLE
	};

	virtual void OnCreate() 
	{
		m_Facing = FACE_RIGHT;
		m_JumpCheckState = JUMP_NOT_AVAILABLE;
		m_AllowJump = true;
		m_HasDoubleJump = false;
		m_MaxRejumpforceTime = .33f;
		m_CurJumpTime = .0f;
		m_TheTimer = &Boon::Timer::GetInstance();
		m_StartPos = GetGameObject().GetTransform().GetPosition();
	}


	virtual void OnEnable() 
	{

	}

	virtual void OnStart() 
	{
		if( GetGameObject().GetRigidbody() == nullptr )
			m_Rigidbody = GetGameObject().AddComponent<Rigidbody>().Lock().Get();
		else m_Rigidbody = GetGameObject().GetComponent<Rigidbody>().Lock().Get();
		m_Rigidbody->SetMass( 2.0f );
		GetGameObject().GetCollider()->SetMaterial( Physics::GetPhysics()->CreateMaterial( .3f, .4f, .1f ) );
		m_SpriteAnimation = GetGameObject().GetComponent<SpriteAnimation>().Lock().Get();

		BOON_ASSERT( m_Rigidbody != nullptr )
		BOON_ASSERT( m_SpriteAnimation != nullptr )

		// register this class as physics observer
		GetGameObject().GetCollider()->Register( GetComponentID(), GetGameObject().GetComponent<PlayerController>().Lock() );
		m_DeadEvent = Shared_ptr<Event_PlayerDied>( new Event_PlayerDied() );
	}

	virtual void Update() 
	{
		if( AllowJump() ) // contact on ground
		{
			if( m_CurJumpTime > .1f )
			{
				m_SpriteAnimation->Stop( "Jump" );
				m_SpriteAnimation->Stop( "DoubleJump" );
				// if velocity -> "running out animation"
				if( m_Rigidbody->GetVelocity().x > .3f )
					m_SpriteAnimation->Play( "Run" );

			}
			m_CurJumpTime = .0f;
			m_HasDoubleJump = false;
		}
		else // airtime
		{
			m_CurJumpTime += m_TheTimer->GetInstance().DeltaTime();
		}
		float magnitude = m_Rigidbody->GetVelocity().SqrMagnitude();

		if( magnitude > 0.2f ) // decreasing velocity
		{
			Vector3 vel = m_Rigidbody->GetVelocity();
			Vector3 newVel = vel * .995f;
			newVel.y = vel.y;
			m_Rigidbody->SetVelocity( newVel );
		}
		else // play idle if vel is too low
		{
			if( AllowJump() )
			{
				m_SpriteAnimation->Play( "Idle" );
			}
			m_Rigidbody->SetVelocity( Vector3::ZERO );
		}
	}

	virtual void LateUpdate() 
	{
		// force new calculation in next update loop
		m_JumpCheckState = JUMP_NOT_AVAILABLE;
	}

	virtual void OnDisable() 
	{

	}

	virtual void OnDestroy() 
	{

	}

public:

	bool			AllowJump()
	{
		if( m_CurJumpTime > 0 && m_CurJumpTime < m_MaxRejumpforceTime )
			return false;
		if( m_JumpCheckState == JUMP_NOT_AVAILABLE )
		{
			RaycastHit hit;
			m_AllowJump = Physics::RayCast(	GetGameObject().GetTransform().GetPosition() - Vector3::UP * 2.5f,
											Vector3::UP * -1.0f, 
											hit,
											.2f,
											true );
			m_JumpCheckState = JUMP_CHECKED;
		}
		return m_AllowJump;
	}

	void			WalkLeft()
	{
		if( m_Facing != FACE_LEFT ) // stop if turn around
		{
			m_Rigidbody->SetVelocity( 0, m_Rigidbody->GetVelocity().y, 0 );
			m_Facing = FACE_LEFT;
			m_SpriteAnimation->GetMaterial().GetBounds().MirrorX();
		}
		m_Rigidbody->AddForce( -40.0f * Vector3::RIGHT, FM_FORCE );
		m_SpriteAnimation->Play( "Run" );
	}

	void			WalkRight()
	{
		if( m_Facing != FACE_RIGHT ) // stop if turn around
		{
			m_Rigidbody->SetVelocity( 0, m_Rigidbody->GetVelocity().y, 0 );
			m_Facing = FACE_RIGHT;
			m_SpriteAnimation->GetMaterial().GetBounds().MirrorX();
		}
		m_Rigidbody->AddForce( 40.0f * Vector3::RIGHT, FM_FORCE );
		m_SpriteAnimation->Play( "Run" );
	}

	void			Jump()
	{

		bool allowJump = AllowJump();
		if( allowJump )
		{
			Vector3 curVel = m_Rigidbody->GetVelocity();
			m_Rigidbody->SetVelocity( curVel.x * .75f, 0, 0 );
			m_Rigidbody->AddForce( 0, 54, 0, FM_IMPULSE );
			m_SpriteAnimation->Play( "Jump" );
		}
		else if( !m_HasDoubleJump )
		{
			float multiply = 1;
			if( m_CurJumpTime >= m_MaxRejumpforceTime )
				multiply = 8.8f;
			m_Rigidbody->AddForce( 0, 5 * multiply, 0, FM_IMPULSE );
			m_HasDoubleJump = true;
			m_SpriteAnimation->Play( "DoubleJump" );
		}
	}

	void			Attack()
	{
		m_SpriteAnimation->Play( "AttackFlip" );
		RaycastHit hit;
		Physics::RayCast( GetGameObject().GetTransform().GetPosition() + Vector3::RIGHT * 2.6f,
						  Vector3::RIGHT, hit, 1.8f, false );
		RaycastHit hit2;

		Physics::RayCast( GetGameObject().GetTransform().GetPosition() + Vector3::RIGHT * -2.6f,
			Vector3::RIGHT * -1.0f, hit2, 1.8f, false );
		
		// "fake Delete"
		if( hit && hit.GameObject->GetName() == "Le_Barrel" )
		{
			hit.GameObject->GetTransform().SetPosition( 200, 200, 0 );
		}
		if( hit2 && hit2.GameObject->GetName() == "Le_Barrel" )
		{
			hit2.GameObject->GetTransform().SetPosition( 200, 200, 0 );
		}
	}

	virtual void OnTriggerEnter( const Collider& collider ) 
	{

	}

	virtual void OnTriggerExit( const Collider& collider ) 
	{

	}

	virtual void OnTriggerStay( const Collider& collider ) 
	{

	}

	virtual void OnCollisionEnter( const Collider& collider ) 
	{
		if( collider.GetGameObject().GetName() == "Le_Barrel" )
		{
			GetGameObject().GetTransform().SetPosition( m_StartPos );
			++m_DeadEvent->m_DeadCount;
			Messenger::GetInstance().Bang( m_DeadEvent );
		}
	}

	virtual void OnCollisionExit( const Collider& collider ) 
	{

	}

	virtual void OnCollisionStay( const Collider& collider ) 
	{

	}

private:
	Vector3							m_StartPos;
	Rigidbody*						m_Rigidbody;
	Facing							m_Facing;
	JumpCheckState					m_JumpCheckState;
	bool							m_AllowJump;
	bool							m_HasDoubleJump;
	float							m_MaxRejumpforceTime;
	float							m_CurJumpTime;
	Boon::Timer*					m_TheTimer;
	SpriteAnimation*				m_SpriteAnimation;
	Shared_ptr<Event_PlayerDied>	m_DeadEvent;
};

